const to_call = function(py) {
  var create = function(o, props) {
    function F() {}
    F.prototype = o
    var inst = new F()
    if (props) {
      for (var name in props) {
        // if (!props.hasOwnProperty(name)) {
        if (!Object.prototype.hasOwnProperty.call(props, name)) {
          continue
        }
        inst[name] = props[name]
      }
    }
    return inst
  }

  var symbols = {}
  var comparators = {}
  var Base = {
    nud: function() {
      throw new Error(this.id + ' undefined as prefix')
    },
    // eslint-disable-next-line no-unused-vars
    led: function(led) {
      throw new Error(this.id + ' undefined as infix')
    },
    toString: function() {
      if (
        this.id === '(constant)' ||
        this.id === '(number)' ||
        this.id === '(name)' ||
        this.id === '(string)'
      ) {
        return [
          this.id.slice(0, this.id.length - 1),
          ' ',
          this.value,
          ')'
        ].join('')
      } else if (this.id === '(end)') {
        return '(end)'
      } else if (this.id === '(comparator)') {
        var repr = ['(comparator', this.expressions[0]]
        for (var i = 0; i < this.operators.length; ++i) {
          repr.push(this.operators[i], this.expressions[i + 1])
        }
        return repr.join(' ') + ')'
      }
      var out = [this.id, this.first, this.second, this.third]
        .filter(function(r) {
          return r
        })
        .join(' ')
      return '(' + out + ')'
    }
  }
  function symbol(id, bp) {
    bp = bp || 0
    var s = symbols[id]
    if (s) {
      if (bp > s.lbp) {
        s.lbp = bp
      }
      return s
    }
    return (symbols[id] = create(Base, {
      id: id,
      lbp: bp
    }))
  }
  function constant(id) {
    var s = symbol(id)
    s.id = '(constant)'
    s.value = id
    s.nud = function() {
      return this
    }
  }
  function prefix(id, bp, nud) {
    symbol(id).nud =
      nud ||
      function() {
        this.first = expression(bp)
        return this
      }
  }
  function infix(id, bp, led) {
    symbol(id, bp).led =
      led ||
      function(left) {
        this.first = left
        this.second = expression(bp)
        return this
      }
  }
  function infixr(id, bp) {
    symbol(id, bp).led = function(left) {
      this.first = left
      this.second = expression(bp - 1)
      return this
    }
  }
  function comparator(id) {
    comparators[id] = true
    var bp = 60
    infix(id, bp, function(left) {
      this.id = '(comparator)'
      this.operators = [id]
      this.expressions = [left, expression(bp)]
      while (token.id in comparators) {
        this.operators.push(token.id)
        advance()
        this.expressions.push(expression(bp))
      }
      return this
    })
  }

  constant('None')
  constant('False')
  constant('True')

  symbol('(number)').nud = function() {
    return this
  }
  symbol('(name)').nud = function() {
    return this
  }
  symbol('(string)').nud = function() {
    return this
  }
  symbol('(end)')

  symbol(':')
  symbol(')')
  symbol(']')
  symbol('}')
  symbol(',')
  symbol('else')

  infix('=', 10, function(left) {
    if (left.id !== '(name)') {
      throw new Error('Expected keyword argument name, got ' + token.id)
    }
    this.first = left
    this.second = expression()
    return this
  })

  symbol('lambda', 20).nud = function() {
    this.first = []
    if (token.id !== ':') {
      for (;;) {
        if (token.id !== '(name)') {
          throw new Error('Excepted an argument name')
        }
        this.first.push(token)
        advance()
        if (token.id !== ',') {
          break
        }
        advance(',')
      }
    }
    advance(':')
    this.second = expression()
    return this
  }
  infix('if', 20, function(left) {
    this.first = left
    this.second = expression()
    advance('else')
    this.third = expression()
    return this
  })

  infixr('or', 30)
  infixr('and', 40)
  prefix('not', 50)

  comparator('in')
  comparator('not in')
  comparator('is')
  comparator('is not')
  comparator('<')
  comparator('<=')
  comparator('>')
  comparator('>=')
  comparator('<>')
  comparator('!=')
  comparator('==')

  infix('|', 70)
  infix('^', 80)
  infix('&', 90)

  infix('<<', 100)
  infix('>>', 100)

  infix('+', 110)
  infix('-', 110)

  infix('*', 120)
  infix('/', 120)
  infix('//', 120)
  infix('%', 120)

  prefix('-', 130)
  prefix('+', 130)
  prefix('~', 130)

  infixr('**', 140)

  infix('.', 150, function(left) {
    if (token.id !== '(name)') {
      throw new Error('Expected attribute name, got ' + token.id)
    }
    this.first = left
    this.second = token
    advance()
    return this
  })
  symbol('(', 150).nud = function() {
    this.first = []
    var comma = false
    if (token.id !== ')') {
      // eslint-disable-next-line no-constant-condition
      while (true) {
        if (token.id === ')') {
          break
        }
        this.first.push(expression())
        if (token.id !== ',') {
          break
        }
        comma = true
        advance(',')
      }
    }
    advance(')')
    if (!this.first.length || comma) {
      return this
    } else {
      return this.first[0]
    }
  }
  symbol('(').led = function(left) {
    this.first = left
    this.second = []
    if (token.id !== ')') {
      for (;;) {
        this.second.push(expression())
        if (token.id !== ',') {
          break
        }
        advance(',')
      }
    }
    advance(')')
    return this
  }
  infix('[', 150, function(left) {
    this.first = left
    this.second = expression()
    advance(']')
    return this
  })
  symbol('[').nud = function() {
    this.first = []
    if (token.id !== ']') {
      for (;;) {
        if (token.id === ']') {
          break
        }
        this.first.push(expression())
        if (token.id !== ',') {
          break
        }
        advance(',')
      }
    }
    advance(']')
    return this
  }

  symbol('{').nud = function() {
    this.first = []
    if (token.id !== '}') {
      for (;;) {
        if (token.id === '}') {
          break
        }
        var key = expression()
        advance(':')
        var value = expression()
        this.first.push([key, value])
        if (token.id !== ',') {
          break
        }
        advance(',')
      }
    }
    advance('}')
    return this
  }

  py.tokenize = (function() {
    function group() {
      return '(' + Array.prototype.join.call(arguments, '|') + ')'
    }

    var Whitespace = '[ \\f\\t]*'

    var Name = '[a-zA-Z_]\\w*'

    var DecNumber = '\\d+(L|l)?'
    var IntNumber = DecNumber
    var PointFloat = group('\\d+\\.\\d*', '\\.\\d+')
    var FloatNumber = PointFloat
    var Number = group(FloatNumber, IntNumber)

    var Operator = group(
      '\\*\\*=?',
      '>>=?',
      '<<=?',
      '<>',
      '!=',
      '//=?',
      '[+\\-*/%&|^=<>]=?',
      '~'
    )
    var Bracket = '[\\[\\]\\(\\)\\{\\}]'
    var Special = '[:;.,`@]'
    var Funny = group(Operator, Bracket, Special)

    var ContStr = group(
      "([uU])?'([^\n'\\\\]*(?:\\\\.[^\n'\\\\]*)*)'",
      '([uU])?"([^\n"\\\\]*(?:\\\\.[^\n"\\\\]*)*)"'
    )
    var PseudoToken = Whitespace + group(Number, Funny, ContStr, Name)

    var number_pattern = new RegExp('^' + Number + '$')
    var string_pattern = new RegExp('^' + ContStr + '$')
    var name_pattern = new RegExp('^' + Name + '$')
    var strip = new RegExp('^' + Whitespace)
    return function tokenize(s) {
      var max = s.length,
        tokens = [],
        start,
        end
      // /g flag makes repeated exec() have memory
      var pseudoprog = new RegExp(PseudoToken, 'g')

      while (pseudoprog.lastIndex < max) {
        var pseudomatch = pseudoprog.exec(s)
        if (!pseudomatch) {
          // if match failed on trailing whitespace, end tokenizing
          if (/^\s+$/.test(s.slice(end))) {
            break
          }
          throw new Error(
            'Failed to tokenize <<' +
              s +
              '>> at index ' +
              (end || 0) +
              '; parsed so far: ' +
              tokens
          )
        }

        start = pseudomatch.index
        end = pseudoprog.lastIndex
        // strip leading space caught by Whitespace
        var token = s.slice(start, end).replace(strip, '')

        if (number_pattern.test(token)) {
          tokens.push(
            create(symbols['(number)'], {
              value: parseFloat(token)
            })
          )
        } else if (string_pattern.test(token)) {
          var m = string_pattern.exec(token)
          var value = m[3] !== undefined ? m[3] : m[5]
          tokens.push(
            create(symbols['(string)'], {
              unicode: !!(m[2] || m[4]),
              value: value
            })
          )
        } else if (token in symbols) {
          var symbol
          // transform 'not in' and 'is not' in a single token
          if (
            token === 'in' &&
            tokens.length > 1 &&
            tokens[tokens.length - 1].id === 'not'
          ) {
            symbol = symbols['not in']
            tokens.pop()
          } else if (
            token === 'not' &&
            tokens.length > 1 &&
            tokens[tokens.length - 1].id === 'is'
          ) {
            symbol = symbols['is not']
            tokens.pop()
          } else {
            symbol = symbols[token]
          }
          tokens.push(create(symbol))
        } else if (name_pattern.test(token)) {
          tokens.push(
            create(symbols['(name)'], {
              value: token
            })
          )
        } else {
          throw new Error(
            'Tokenizing failure of <<' +
              s +
              '>> at index ' +
              start +
              ' for token [[' +
              token +
              ']]' +
              '; parsed so far: ' +
              tokens
          )
        }
      }
      tokens.push(create(symbols['(end)']))
      return tokens
    }
  })()

  var token, next
  function expression(rbp) {
    rbp = rbp || 0
    var t = token
    token = next()
    var left = t.nud()
    while (rbp < token.lbp) {
      t = token
      token = next()
      left = t.led(left)
    }
    return left
  }
  function advance(id) {
    if (id && token.id !== id) {
      throw new Error('Expected "' + id + '", got "' + token.id + '"')
    }
    token = next()
  }

  function PY_ensurepy(val, name) {
    switch (val) {
      case undefined:
        throw new Error("NameError: name '" + name + "' is not defined")
      case null:
        return py.None
      case true:
        return py.True
      case false:
        return py.False
    }

    var fn = function() {}
    fn.prototype = py.object
    if (py.PY_isInstance(val, py.object) || py.PY_isSubclass(val, py.object)) {
      return val
    }

    switch (typeof val) {
      case 'number':
        return py.float.fromJSON(val)
      case 'string':
        return py.str.fromJSON(val)
      case 'function':
        return py.PY_def.fromJSON(val)
    }

    switch (val.constructor) {
      case Object:
        // TODO: why py.object instead of py.dict?
        var o = py.PY_call(py.object)
        for (var prop in val) {
          // if (val.hasOwnProperty(prop)) {
          if (!Object.prototype.hasOwnProperty.call(val, prop)) {
            o[prop] = val[prop]
          }
        }
        return o
      case Array:
        return py.list.fromJSON(val)
    }

    throw new Error('Could not convert ' + val + ' to a pyval')
  }

  var typename = function(obj) {
    if (obj.__class__) {
      // py type
      return obj.__class__.__name__
    } else if (typeof obj !== 'object') {
      // JS primitive
      return typeof obj
    } else {
      // JS object
      return obj.constructor.name
    }
  }
  // JSAPI, JS-level utility functions for implementing new py.js
  // types
  py.py = {}

  py.PY_parseArgs = function PY_parseArgs(argument, format) {
    var out = {}
    var args = argument[0]
    var kwargs = {}
    for (var k in argument[1]) {
      // if (!argument[1].hasOwnProperty(k)) {
      if (!Object.prototype.hasOwnProperty.call(argument[1], k)) {
        continue
      }
      kwargs[k] = argument[1][k]
    }
    if (typeof format === 'string') {
      format = format.split(/\s+/)
    }
    var name = function(spec) {
      if (typeof spec === 'string') {
        return spec
      } else if (spec instanceof Array && spec.length === 2) {
        return spec[0]
      }
      throw new Error(
        'TypeError: unknown format specification ' + JSON.stringify(spec)
      )
    }
    var spec
    // TODO: ensure all format arg names are actual names?
    for (var i = 0; i < args.length; ++i) {
      spec = format[i]
      // spec list ended, or specs switching to keyword-only
      if (!spec || spec === '*') {
        throw new Error(
          'TypeError: function takes exactly ' +
            (i - 1) +
            ' positional arguments (' +
            args.length +
            ' given'
        )
      } else if (/^\*\w/.test(spec)) {
        // *args, final
        out[name(spec.slice(1))] = args.slice(i)
        break
      }

      out[name(spec)] = args[i]
    }
    for (var j = i; j < format.length; ++j) {
      spec = format[j]
      var n = name(spec)

      if (n in out) {
        throw new Error(
          'TypeError: function got multiple values ' +
            "for keyword argument '" +
            // kwarg +
            "'"
        )
      }
      if (/^\*\*\w/.test(n)) {
        // **kwarg
        out[n.slice(2)] = kwargs
        kwargs = {}
        break
      }
      if (n in kwargs) {
        out[n] = kwargs[n]
        // Remove from args map
        delete kwargs[n]
      }
    }
    // Ensure all keyword arguments were consumed
    for (var key in kwargs) {
      throw new Error(
        "TypeError: function got an unexpected keyword argument '" + key + "'"
      )
    }

    // Fixup args count if there's a kwonly flag (or an *args)
    var kwonly = 0
    for (var k1 = 0; k1 < format.length; ++k1) {
      if (/^\*/.test(format[k1])) {
        kwonly = 1
        break
      }
    }
    // Check that all required arguments have been matched, add
    // optional values
    for (var k2 = 0; k2 < format.length; ++k2) {
      spec = format[k2]
      var n2 = name(spec)
      // kwonly, va_arg or matched argument
      if (/^\*/.test(n2) || n2 in out) {
        continue
      }
      // Unmatched required argument
      if (!(spec instanceof Array)) {
        throw new Error(
          'TypeError: function takes exactly ' +
            (format.length - kwonly) +
            ' arguments'
        )
      }
      // Set default value
      out[n2] = spec[1]
    }

    return out
  }

  py.PY_hasAttr = function(o, attr_name) {
    try {
      py.PY_getAttr(o, attr_name)
      return true
    } catch (e) {
      return false
    }
  }
  py.PY_getAttr = function(o, attr_name) {
    return PY_ensurepy(o.__getattribute__(attr_name))
  }
  py.PY_str = function(o) {
    var v = o.__str__()
    if (py.PY_isInstance(v, py.str)) {
      return v
    }
    throw new Error(
      'TypeError: __str__ returned non-string (type ' + typename(v) + ')'
    )
  }
  py.PY_isInstance = function(inst, cls) {
    var fn = function() {}
    fn.prototype = cls
    return inst instanceof fn
  }
  py.PY_isSubclass = function(derived, cls) {
    var fn = function() {}
    fn.prototype = cls
    return derived === cls || derived instanceof fn
  }
  py.PY_call = function(callable, args, kwargs) {
    if (!args) {
      args = []
    }
    if (typeof args === 'object' && !(args instanceof Array)) {
      kwargs = args
      args = []
    }
    if (!kwargs) {
      kwargs = {}
    }
    if (callable.__is_type) {
      // class hack
      var instance = callable.__new__.call(callable, args, kwargs)
      var typ = function() {}
      typ.prototype = callable
      if (instance instanceof typ) {
        instance.__init__.call(instance, args, kwargs)
      }
      return instance
    }
    return callable.__call__(args, kwargs)
  }
  py.PY_isTrue = function(o) {
    var res = o.__nonzero__()
    if (res === py.True) {
      return true
    }
    if (res === py.False) {
      return false
    }
    throw new Error(
      'TypeError: __nonzero__ should return bool, returned ' + typename(res)
    )
  }
  py.PY_not = function(o) {
    return !py.PY_isTrue(o)
  }
  py.PY_size = function(o) {
    if (!o.__len__) {
      throw new Error(
        "TypeError: object of type '" + typename(o) + "' has no len()"
      )
    }
    var v = o.__len__()
    if (typeof v !== 'number') {
      throw new Error('TypeError: a number is required')
    }
    return v
  }
  py.PY_getItem = function(o, key) {
    if (!('__getitem__' in o)) {
      throw new Error(
        "TypeError: '" + typename(o) + "' object is unsubscriptable"
      )
    }
    if (!py.PY_isInstance(key, py.object)) {
      throw new Error(
        "TypeError: '" + typename(key) + "' is not a py.js object"
      )
    }
    var res = o.__getitem__(key)
    if (!py.PY_isInstance(key, py.object)) {
      throw new Error(
        'TypeError: __getitem__ must return a py.js object, got ' +
          typename(res)
      )
    }
    return res
  }
  py.PY_setItem = function(o, key, v) {
    if (!('__setitem__' in o)) {
      throw new Error(
        "TypeError: '" +
          typename(o) +
          "' object does not support item assignment"
      )
    }
    if (!py.PY_isInstance(key, py.object)) {
      throw new Error(
        "TypeError: '" + typename(key) + "' is not a py.js object"
      )
    }
    if (!py.PY_isInstance(v, py.object)) {
      throw new Error("TypeError: '" + typename(v) + "' is not a py.js object")
    }
    o.__setitem__(key, v)
  }

  py.PY_add = function(o1, o2) {
    return PY_op(o1, o2, '+')
  }
  py.PY_subtract = function(o1, o2) {
    return PY_op(o1, o2, '-')
  }
  py.PY_multiply = function(o1, o2) {
    return PY_op(o1, o2, '*')
  }
  py.PY_divide = function(o1, o2) {
    return PY_op(o1, o2, '/')
  }
  py.PY_negative = function(o) {
    if (!o.__neg__) {
      throw new Error(
        "TypeError: bad operand for unary -: '" + typename(o) + "'"
      )
    }
    return o.__neg__()
  }
  py.PY_positive = function(o) {
    if (!o.__pos__) {
      throw new Error(
        "TypeError: bad operand for unary +: '" + typename(o) + "'"
      )
    }
    return o.__pos__()
  }

  // Builtins
  py.type = function type(name, bases, dict) {
    if (typeof name !== 'string') {
      throw new Error('ValueError: a class name should be a string')
    }
    if (!bases || bases.length === 0) {
      bases = [py.object]
    } else if (bases.length > 1) {
      throw new Error(
        "ValueError: can't provide multiple bases for a " + 'new type'
      )
    }
    var base = bases[0]
    var ClassObj = create(base)
    if (dict) {
      for (var k in dict) {
        // if (!dict.hasOwnProperty(k)) {
        if (!Object.prototype.hasOwnProperty.call(dict, k)) {
          continue
        }
        ClassObj[k] = dict[k]
      }
    }
    ClassObj.__class__ = ClassObj
    ClassObj.__name__ = name
    ClassObj.__bases__ = bases
    ClassObj.__is_type = true

    return ClassObj
  }
  py.type.__call__ = function() {
    var args = py.PY_parseArgs(arguments, ['object'])
    return args.object.__class__
  }

  var hash_counter = 0
  py.object = py.type('object', [{}], {
    __new__: function() {
      // If ``this`` isn't the class object, this is going to be
      // beyond fucked up
      var inst = create(this)
      inst.__is_type = false
      return inst
    },
    __init__: function() {},
    // Basic customization
    __hash__: function() {
      if (this._hash) {
        return this._hash
      }
      // tagged counter, to avoid collisions with e.g. number hashes
      return (this._hash = '\0\0\0' + String(hash_counter++))
    },
    __eq__: function(other) {
      return this === other ? py.True : py.False
    },
    __ne__: function(other) {
      if (py.PY_isTrue(this.__eq__(other))) {
        return py.False
      } else {
        return py.True
      }
    },
    __lt__: function() {
      return py.NotImplemented
    },
    __le__: function() {
      return py.NotImplemented
    },
    __ge__: function() {
      return py.NotImplemented
    },
    __gt__: function() {
      return py.NotImplemented
    },
    __str__: function() {
      return this.__unicode__()
    },
    __unicode__: function() {
      return py.str.fromJSON('<' + typename(this) + ' object>')
    },
    __nonzero__: function() {
      return py.True
    },
    // Attribute access
    __getattribute__: function(name) {
      if (name in this) {
        var val = this[name]
        if (typeof val === 'object' && '__get__' in val) {
          // TODO: second argument should be class
          return val.__get__(this, py.PY_call(py.type, [this]))
        }
        // if (typeof val === 'function' && !this.hasOwnProperty(name)) {
        // if (typeof val === 'function' && !this.hasOwnProperty(name)) {
        if (
          typeof val === 'function' &&
          !Object.prototype.hasOwnProperty.call(this, name)
        ) {
          // val is a method from the class
          return PY_instancemethod.fromJSON(val, this)
        }
        return val
      }
      if ('__getattr__' in this) {
        return this.__getattr__(name)
      }
      throw new Error("AttributeError: object has no attribute '" + name + "'")
    },
    __setattr__: function(name, value) {
      if (name in this && '__set__' in this[name]) {
        this[name].__set__(this, value)
      }
      this[name] = value
    },
    // no delattr, because no 'del' statement

    // Conversion
    toJSON: function() {
      throw new Error(this.constructor.name + ' can not be converted to JSON')
    }
  })
  var NoneType = py.type('NoneType', null, {
    __nonzero__: function() {
      return py.False
    },
    toJSON: function() {
      return null
    }
  })
  py.None = py.PY_call(NoneType)
  var NotImplementedType = py.type('NotImplementedType', null, {})
  py.NotImplemented = py.PY_call(NotImplementedType)
  var booleans_initialized = false
  py.bool = py.type('bool', null, {
    __new__: function() {
      if (!booleans_initialized) {
        return py.object.__new__.apply(this)
      }

      var ph = {}
      var args = py.PY_parseArgs(arguments, [['value', ph]])
      if (args.value === ph) {
        return py.False
      }
      return py.PY_isTrue(args.value) ? py.True : py.False
    },
    __str__: function() {
      return py.str.fromJSON(this === py.True ? 'True' : 'False')
    },
    __nonzero__: function() {
      return this
    },
    fromJSON: function(val) {
      return val ? py.True : py.False
    },
    toJSON: function() {
      return this === py.True
    }
  })
  py.True = py.PY_call(py.bool)
  py.False = py.PY_call(py.bool)
  booleans_initialized = true
  py.float = py.type('float', null, {
    __init__: function() {
      var placeholder = {}
      var args = py.PY_parseArgs(arguments, [['value', placeholder]])
      var value = args.value
      if (value === placeholder) {
        this._value = 0
        return
      }
      if (py.PY_isInstance(value, py.float)) {
        this._value = value._value
      }
      if (py.PY_isInstance(value, py.object) && '__float__' in value) {
        var res = value.__float__()
        if (py.PY_isInstance(res, py.float)) {
          this._value = res._value
          return
        }
        throw new Error(
          'TypeError: __float__ returned non-float (type ' + typename(res) + ')'
        )
      }
      throw new Error(
        'TypeError: float() argument must be a string or a number'
      )
    },
    __str__: function() {
      return py.str.fromJSON(String(this._value))
    },
    __eq__: function(other) {
      return this._value === other._value ? py.True : py.False
    },
    __lt__: function(other) {
      if (!py.PY_isInstance(other, py.float)) {
        return py.NotImplemented
      }
      return this._value < other._value ? py.True : py.False
    },
    __le__: function(other) {
      if (!py.PY_isInstance(other, py.float)) {
        return py.NotImplemented
      }
      return this._value <= other._value ? py.True : py.False
    },
    __gt__: function(other) {
      if (!py.PY_isInstance(other, py.float)) {
        return py.NotImplemented
      }
      return this._value > other._value ? py.True : py.False
    },
    __ge__: function(other) {
      if (!py.PY_isInstance(other, py.float)) {
        return py.NotImplemented
      }
      return this._value >= other._value ? py.True : py.False
    },
    __abs__: function() {
      return py.float.fromJSON(Math.abs(this._value))
    },
    __add__: function(other) {
      if (!py.PY_isInstance(other, py.float)) {
        return py.NotImplemented
      }
      return py.float.fromJSON(this._value + other._value)
    },
    __mod__: function(other) {
      if (!py.PY_isInstance(other, py.float)) {
        return py.NotImplemented
      }
      return py.float.fromJSON(this._value % other._value)
    },
    __neg__: function() {
      return py.float.fromJSON(-this._value)
    },
    __sub__: function(other) {
      if (!py.PY_isInstance(other, py.float)) {
        return py.NotImplemented
      }
      return py.float.fromJSON(this._value - other._value)
    },
    __mul__: function(other) {
      if (!py.PY_isInstance(other, py.float)) {
        return py.NotImplemented
      }
      return py.float.fromJSON(this._value * other._value)
    },
    __pow__: function(other) {
      if (!py.PY_isInstance(other, py.float)) {
        return py.NotImplemented
      }
      return py.float.fromJSON(this._value ** other._value)
    },
    __div__: function(other) {
      if (!py.PY_isInstance(other, py.float)) {
        return py.NotImplemented
      }
      return py.float.fromJSON(this._value / other._value)
    },
    __nonzero__: function() {
      return this._value ? py.True : py.False
    },
    fromJSON: function(v) {
      if (!(typeof v === 'number')) {
        throw new Error('py.float.fromJSON can only take numbers')
      }
      var instance = py.PY_call(py.float)
      instance._value = v
      return instance
    },
    toJSON: function() {
      return this._value
    }
  })
  py.str = py.type('str', null, {
    __init__: function() {
      var placeholder = {}
      var args = py.PY_parseArgs(arguments, [['value', placeholder]])
      var s = args.value
      if (s === placeholder) {
        this._value = ''
        return
      }
      this._value = py.PY_str(s)._value
    },
    __hash__: function() {
      // return '\1\0\1' + this._value
      return '\0x1\0\0x1' + this._value
    },
    __str__: function() {
      return this
    },
    __eq__: function(other) {
      if (py.PY_isInstance(other, py.str) && this._value === other._value) {
        return py.True
      }
      return py.False
    },
    __lt__: function(other) {
      if (py.PY_not(py.PY_call(py.isinstance, [other, py.str]))) {
        return py.NotImplemented
      }
      return this._value < other._value ? py.True : py.False
    },
    __le__: function(other) {
      if (!py.PY_isInstance(other, py.str)) {
        return py.NotImplemented
      }
      return this._value <= other._value ? py.True : py.False
    },
    __gt__: function(other) {
      if (!py.PY_isInstance(other, py.str)) {
        return py.NotImplemented
      }
      return this._value > other._value ? py.True : py.False
    },
    __ge__: function(other) {
      if (!py.PY_isInstance(other, py.str)) {
        return py.NotImplemented
      }
      return this._value >= other._value ? py.True : py.False
    },
    __add__: function(other) {
      if (!py.PY_isInstance(other, py.str)) {
        return py.NotImplemented
      }
      return py.str.fromJSON(this._value + other._value)
    },
    __nonzero__: function() {
      return this._value.length ? py.True : py.False
    },
    __contains__: function(s) {
      return this._value.indexOf(s._value) !== -1 ? py.True : py.False
    },
    fromJSON: function(s) {
      if (typeof s === 'string') {
        var instance = py.PY_call(py.str)
        instance._value = s
        return instance
      }
      throw new Error('str.fromJSON can only take strings')
    },
    toJSON: function() {
      return this._value
    }
  })
  py.tuple = py.type('tuple', null, {
    __init__: function() {
      this._values = []
    },
    __len__: function() {
      return this._values.length
    },
    __nonzero__: function() {
      return py.PY_size(this) > 0 ? py.True : py.False
    },
    __contains__: function(value) {
      for (var i = 0, len = this._values.length; i < len; ++i) {
        if (py.PY_isTrue(this._values[i].__eq__(value))) {
          return py.True
        }
      }
      return py.False
    },
    __getitem__: function(index) {
      return this._values[index.toJSON()]
    },
    toJSON: function() {
      var out = []
      for (var i = 0; i < this._values.length; ++i) {
        out.push(this._values[i].toJSON())
      }
      return out
    },
    fromJSON: function(ar) {
      if (!(ar instanceof Array)) {
        throw new Error('Can only create a py.tuple from an Array')
      }
      var t = py.PY_call(py.tuple)
      for (var i = 0; i < ar.length; ++i) {
        t._values.push(PY_ensurepy(ar[i]))
      }
      return t
    }
  })
  py.list = py.type('list', null, {
    __init__: function() {
      this._values = []
    },
    __len__: function() {
      return this._values.length
    },
    __nonzero__: function() {
      return this.__len__ > 0 ? py.True : py.False
    },
    __contains__: function(value) {
      for (var i = 0, len = this._values.length; i < len; ++i) {
        if (py.PY_isTrue(this._values[i].__eq__(value))) {
          return py.True
        }
      }
      return py.False
    },
    __getitem__: function(index) {
      return this._values[index.toJSON()]
    },
    toJSON: function() {
      var out = []
      for (var i = 0; i < this._values.length; ++i) {
        out.push(this._values[i].toJSON())
      }
      return out
    },
    fromJSON: function(ar) {
      if (!(ar instanceof Array)) {
        throw new Error('Can only create a py.tuple from an Array')
      }
      var t = py.PY_call(py.tuple)
      for (var i = 0; i < ar.length; ++i) {
        t._values.push(PY_ensurepy(ar[i]))
      }
      return t
    }
  })
  // _.defaults(py.list, py.tuple) // Copy attributes not redefined in type list
  // console.log('1191', py.list, py.tuple, typeof py.tuple)
  py.dict = py.type('dict', null, {
    __init__: function() {
      this._store = {}
    },
    __getitem__: function(key) {
      var h = key.__hash__()
      if (!(h in this._store)) {
        throw new Error("KeyError: '" + key.toJSON() + "'")
      }
      return this._store[h][1]
    },
    __setitem__: function(key, value) {
      this._store[key.__hash__()] = [key, value]
    },
    __len__: function() {
      return Object.keys(this._store).length
    },
    __nonzero__: function() {
      return py.PY_size(this) > 0 ? py.True : py.False
    },
    get: function() {
      var args = py.PY_parseArgs(arguments, ['k', ['d', py.None]])
      var h = args.k.__hash__()
      if (!(h in this._store)) {
        return args.d
      }
      return this._store[h][1]
    },
    fromJSON: function(d) {
      var instance = py.PY_call(py.dict)
      for (var k in d || {}) {
        // if (!d.hasOwnProperty(k)) {
        if (!Object.prototype.hasOwnProperty.call(d, k)) {
          continue
        }
        instance.__setitem__(py.str.fromJSON(k), PY_ensurepy(d[k]))
      }
      return instance
    },
    toJSON: function() {
      var out = {}
      for (var k in this._store) {
        var item = this._store[k]
        out[item[0].toJSON()] = item[1].toJSON()
      }
      return out
    }
  })
  py.PY_def = py.type('function', null, {
    __call__: function() {
      // don't want to rewrite __call__ for instancemethod
      return this._func.apply(this._inst, arguments)
    },
    fromJSON: function(nativefunc) {
      var instance = py.PY_call(py.PY_def)
      instance._inst = null
      instance._func = nativefunc
      return instance
    },
    toJSON: function() {
      return this._func
    }
  })
  py.classmethod = py.type('classmethod', null, {
    __init__: function() {
      var args = py.PY_parseArgs(arguments, 'function')
      this._func = args['function']
    },
    __get__: function(obj, type) {
      return PY_instancemethod.fromJSON(this._func, type)
    },
    fromJSON: function(func) {
      return py.PY_call(py.classmethod, [func])
    }
  })
  var PY_instancemethod = py.type('instancemethod', [py.PY_def], {
    fromJSON: function(nativefunc, instance) {
      var inst = py.PY_call(PY_instancemethod)
      // could also use bind?
      inst._inst = instance
      inst._func = nativefunc
      return inst
    }
  })

  py.abs = new py.PY_def.fromJSON(function abs() {
    var args = py.PY_parseArgs(arguments, ['number'])
    if (!args.number.__abs__) {
      throw new Error(
        "TypeError: bad operand type for abs(): '" + typename(args.number) + "'"
      )
    }
    return args.number.__abs__()
  })
  py.len = new py.PY_def.fromJSON(function len() {
    var args = py.PY_parseArgs(arguments, ['object'])
    return py.float.fromJSON(py.PY_size(args.object))
  })
  py.isinstance = new py.PY_def.fromJSON(function isinstance() {
    var args = py.PY_parseArgs(arguments, ['object', 'class'])
    return py.PY_isInstance(args.object, args['class']) ? py.True : py.False
  })
  py.issubclass = new py.PY_def.fromJSON(function issubclass() {
    var args = py.PY_parseArgs(arguments, ['C', 'B'])
    return py.PY_isSubclass(args.C, args.B) ? py.True : py.False
  })

  /**
   * Implements the decoding of Python string literals (embedded in
   * JS strings) into actual JS strings. This includes the decoding
   * of escapes into their corresponding JS
   * characters/codepoints/whatever.
   *
   * The ``unicode`` flags notes whether the literal should be
   * decoded as a bytestring literal or a unicode literal, which
   * pretty much only impacts decoding (or not) of unicode escapes
   * at this point since bytestrings are not technically handled
   * (everything is decoded to JS "unicode" strings)
   *
   * Eventurally, ``str`` could eventually use typed arrays, that'd
   * be interesting...
   */
  var PY_decode_string_literal = function(str, unicode) {
    var out = [],
      code
    // Directly maps a single escape code to an output
    // character
    var direct_map = {
      '\\': '\\',
      '"': '"',
      "'": "'",
      a: '\x07',
      b: '\x08',
      f: '\x0c',
      n: '\n',
      r: '\r',
      t: '\t',
      v: '\v'
    }

    for (var i = 0; i < str.length; ++i) {
      if (str[i] !== '\\') {
        out.push(str[i])
        continue
      }
      var escape = str[i + 1]
      if (escape in direct_map) {
        out.push(direct_map[escape])
        ++i
        continue
      }

      switch (escape) {
        // Ignored
        case '\n':
          ++i
          continue
        // Character named name in the Unicode database (Unicode only)
        case 'N':
          if (!unicode) {
            break
          }
          throw Error('SyntaxError: \\N{} escape not implemented')
        case 'u':
          if (!unicode) {
            break
          }
          var uni = str.slice(i + 2, i + 6)
          if (!/[0-9a-f]{4}/i.test(uni)) {
            throw new Error(
              [
                "SyntaxError: (unicode error) 'unicodeescape' codec",
                " can't decode bytes in position ",
                i,
                '-',
                i + 4,
                ': truncated \\uXXXX escape'
              ].join('')
            )
          }
          code = parseInt(uni, 16)
          out.push(String.fromCharCode(code))
          // escape + 4 hex digits
          i += 5
          continue
        case 'U':
          if (!unicode) {
            break
          }
          // TODO: String.fromCodePoint
          throw Error('SyntaxError: \\U escape not implemented')
        case 'x':
          // get 2 hex digits
          var hex = str.slice(i + 2, i + 4)
          if (!/[0-9a-f]{2}/i.test(hex)) {
            if (!unicode) {
              throw new Error('ValueError: invalid \\x escape')
            }
            throw new Error(
              [
                "SyntaxError: (unicode error) 'unicodeescape'",
                " codec can't decode bytes in position ",
                i,
                '-',
                i + 2,
                ': truncated \\xXX escape'
              ].join('')
            )
          }
          code = parseInt(hex, 16)
          out.push(String.fromCharCode(code))
          // skip escape + 2 hex digits
          i += 3
          continue
        default:
          // Check if octal
          if (!/[0-8]/.test(escape)) {
            break
          }
          var r = /[0-8]{1,3}/g
          r.lastIndex = i + 1
          var m = r.exec(str)
          var oct = m[0]
          code = parseInt(oct, 8)
          out.push(String.fromCharCode(code))
          // skip matchlength
          i += oct.length
          continue
      }
      out.push('\\')
    }

    return out.join('')
  }
  // All binary operators with fallbacks, so they can be applied generically
  var PY_operators = {
    '==': [
      'eq',
      'eq',
      function(a, b) {
        return a === b
      }
    ],
    '!=': [
      'ne',
      'ne',
      function(a, b) {
        return a !== b
      }
    ],
    '<>': [
      'ne',
      'ne',
      function(a, b) {
        return a !== b
      }
    ],
    '<': [
      'lt',
      'gt',
      function(a, b) {
        return a.__class__.__name__ < b.__class__.__name__
      }
    ],
    '<=': [
      'le',
      'ge',
      function(a, b) {
        return a.__class__.__name__ <= b.__class__.__name__
      }
    ],
    '>': [
      'gt',
      'lt',
      function(a, b) {
        return a.__class__.__name__ > b.__class__.__name__
      }
    ],
    '>=': [
      'ge',
      'le',
      function(a, b) {
        return a.__class__.__name__ >= b.__class__.__name__
      }
    ],

    '+': ['add', 'radd'],
    '-': ['sub', 'rsub'],
    '*': ['mul', 'rmul'],
    '/': ['div', 'rdiv'],
    '//': ['floordiv', 'rfloordiv'],
    '%': ['mod', 'rmod'],
    '**': ['pow', 'rpow'],
    '<<': ['lshift', 'rlshift'],
    '>>': ['rshift', 'rrshift'],
    '&': ['and', 'rand'],
    '^': ['xor', 'rxor'],
    '|': ['or', 'ror']
  }
  /**
   * Implements operator fallback/reflection.
   *
   * First two arguments are the objects to apply the operator on,
   * in their actual order (ltr).
   *
   * Third argument is the actual operator.
   *
   * If the operator methods raise exceptions, those exceptions are
   * not intercepted.
   */
  var PY_op = function(o1, o2, op) {
    var r
    var methods = PY_operators[op]
    var forward = '__' + methods[0] + '__',
      reverse = '__' + methods[1] + '__'
    var otherwise = methods[2]

    if (forward in o1 && (r = o1[forward](o2)) !== py.NotImplemented) {
      return r
    }
    if (reverse in o2 && (r = o2[reverse](o1)) !== py.NotImplemented) {
      return r
    }
    if (otherwise) {
      return PY_ensurepy(otherwise(o1, o2))
    }
    throw new Error(
      'TypeError: unsupported operand type(s) for ' +
        op +
        ": '" +
        typename(o1) +
        "' and '" +
        typename(o2) +
        "'"
    )
  }

  var PY_builtins = {
    type: py.type,

    None: py.None,
    True: py.True,
    False: py.False,
    NotImplemented: py.NotImplemented,

    object: py.object,
    bool: py.bool,
    float: py.float,
    str: py.str,
    unicode: py.unicode,
    tuple: py.tuple,
    list: py.list,
    dict: py.dict,

    abs: py.abs,
    len: py.len,
    isinstance: py.isinstance,
    issubclass: py.issubclass,
    classmethod: py.classmethod
  }

  py.parse = function(toks) {
    var index = 0
    token = toks[0]
    next = function() {
      return toks[++index]
    }
    return expression()
  }
  var evaluate_operator = function(operator, a, b) {
    switch (operator) {
      case 'is':
        return a === b ? py.True : py.False
      case 'is not':
        return a !== b ? py.True : py.False
      case 'in':
        return b.__contains__(a)
      case 'not in':
        return py.PY_isTrue(b.__contains__(a)) ? py.False : py.True
      case '==':
      case '!=':
      case '<>':
      case '<':
      case '<=':
      case '>':
      case '>=':
        return PY_op(a, b, operator)
    }
    throw new Error('SyntaxError: unknown comparator [[' + operator + ']]')
  }
  py.evaluate = function(expr, context) {
    // console.log('py.evaluate', expr, context)
    context = context || {}
    switch (expr.id) {
      case '(name)':
        var val = context[expr.value]
        if (val === undefined && expr.value in PY_builtins) {
          return PY_builtins[expr.value]
        }
        return PY_ensurepy(val, expr.value)
      case '(string)':
        return py.str.fromJSON(
          PY_decode_string_literal(expr.value, expr.unicode)
        )
      case '(number)':
        return py.float.fromJSON(expr.value)
      case '(constant)':
        switch (expr.value) {
          case 'None':
            return py.None
          case 'False':
            return py.False
          case 'True':
            return py.True
        }
        throw new Error("SyntaxError: unknown constant '" + expr.value + "'")
      case '(comparator)':
        var result,
          left = py.evaluate(expr.expressions[0], context)
        for (var i = 0; i < expr.operators.length; ++i) {
          result = evaluate_operator(
            expr.operators[i],
            left,
            (left = py.evaluate(expr.expressions[i + 1], context))
          )
          if (py.PY_not(result)) {
            return py.False
          }
        }
        return py.True
      case 'not':
        return py.PY_isTrue(py.evaluate(expr.first, context))
          ? py.False
          : py.True
      case 'and':
        var and_first = py.evaluate(expr.first, context)
        if (py.PY_isTrue(and_first.__nonzero__())) {
          return py.evaluate(expr.second, context)
        }
        return and_first
      case 'or':
        var or_first = py.evaluate(expr.first, context)
        if (py.PY_isTrue(or_first.__nonzero__())) {
          return or_first
        }
        return py.evaluate(expr.second, context)
      case '(':
        if (expr.second) {
          var callable = py.evaluate(expr.first, context)
          var args = [],
            kwargs = {}
          for (var jj = 0; jj < expr.second.length; ++jj) {
            var arg = expr.second[jj]
            if (arg.id !== '=') {
              // arg
              args.push(py.evaluate(arg, context))
            } else {
              // kwarg
              kwargs[arg.first.value] = py.evaluate(arg.second, context)
            }
          }
          return py.PY_call(callable, args, kwargs)
        }
        var tuple_exprs = expr.first,
          tuple_values = []
        for (var j = 0, len = tuple_exprs.length; j < len; ++j) {
          tuple_values.push(py.evaluate(tuple_exprs[j], context))
        }
        return py.tuple.fromJSON(tuple_values)
      case '[':
        if (expr.second) {
          return py.PY_getItem(
            py.evaluate(expr.first, context),
            py.evaluate(expr.second, context)
          )
        }
        var list_exprs = expr.first,
          list_values = []
        for (var k = 0; k < list_exprs.length; ++k) {
          list_values.push(py.evaluate(list_exprs[k], context))
        }
        return py.list.fromJSON(list_values)
      case '{':
        var dict_exprs = expr.first,
          dict = py.PY_call(py.dict)
        for (var l = 0; l < dict_exprs.length; ++l) {
          py.PY_setItem(
            dict,
            py.evaluate(dict_exprs[l][0], context),
            py.evaluate(dict_exprs[l][1], context)
          )
        }
        return dict
      case '.':
        if (expr.second.id !== '(name)') {
          throw new Error('SyntaxError: ' + expr)
        }
        return py.PY_getAttr(
          py.evaluate(expr.first, context),
          expr.second.value
        )
      // numerical operators
      case '~':
        return py.evaluate(expr.first, context).__invert__()
      case '+':
        if (!expr.second) {
          return py.PY_positive(py.evaluate(expr.first, context))
        }
        return PY_op(
          py.evaluate(expr.first, context),
          py.evaluate(expr.second, context),
          expr.id
        )

      case '-':
        if (!expr.second) {
          return py.PY_negative(py.evaluate(expr.first, context))
        }
        return PY_op(
          py.evaluate(expr.first, context),
          py.evaluate(expr.second, context),
          expr.id
        )
      case '*':
      case '/':
      case '//':
      case '%':
      case '**':
      case '<<':
      case '>>':
      case '&':
      case '^':
      case '|':
        return PY_op(
          py.evaluate(expr.first, context),
          py.evaluate(expr.second, context),
          expr.id
        )

      default:
        throw new Error('SyntaxError: Unknown node [[' + expr.id + ']]')
    }
  }
  py.eval = function(str, context) {
    // console.log(str, context)
    return py.evaluate(py.parse(py.tokenize(str)), context).toJSON()
  }
}

const py = {}

to_call(py)
// to_call(typeof exports === 'undefined' ? py : exports)
export default py
